﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Linq.Expressions;
using System.Reflection;
using Phoenix.ReflectionExtensions;
using System.Collections;
using PServiceBus.Core.Runtime.Extensions;
using PServiceBus.Core.Runtime;

namespace PServiceBus.WCF.NetTcpTransport {
    [Serializable]
    [DataContract]
    public class OperationCallInfo {

        private static readonly Type _dictType = typeof(IDictionary);
        private static readonly Type _stringType = typeof(String);
        private static readonly Type _listType = typeof(IList);
        private static readonly string _listStr = String.Intern("list");
        private static readonly string _dictStr = String.Intern("dict");
        private static readonly string _dictKey = String.Intern("dictKey");
        private static readonly string _dictValue = String.Intern("dictValue");
        private static readonly string _dotNetTypeStr = String.Intern("dotNetType");
        private static readonly string _typeIdentifier = String.Intern("___type");
        private static readonly string _typeNameIdentifier = String.Intern("___typename");
        private static readonly string _typeNamespaceIdentifier = String.Intern("___typenamespace");


        [field:NonSerialized]
        [MessageShark.MessageSharkIgnore]
        private string _globalNamespace;

        [field:NonSerialized]
        [MessageShark.MessageSharkIgnore]
        private Dictionary<string, string> _typeNamespaces;

        public static OperationCallInfo Select<TServiceInterface>(NetTcpTransport transport, string operationName)
        where TServiceInterface : class {
            var type = typeof(TServiceInterface);
            var methodInfo = type.GetMethodEx(operationName);
            
            var operationCallInfo = 
                new OperationCallInfo() { ServiceType = type.Name, OperationName = operationName, 
                _globalNamespace = transport.GlobalTypeNamespace,
                _typeNamespaces = transport.TypeNamespaces};

            //operationCallInfo.ParameterInfos = operationCallInfo.GetParameterInfos(methodInfo);
            var parameterInfos = operationCallInfo.GetParameterInfos(methodInfo);
            operationCallInfo.ParameterInfos = parameterInfos.Select(param => param.ToJson()).ToList();

            return operationCallInfo;
        }

        private List<Dictionary<string, object>> GetParameterInfos(MethodInfo methodInfo) {
            var parameterInfos = new List<Dictionary<string, object>>();
            var parameters = methodInfo.GetParameters();
            for (var i = 0; i < parameters.Length; i++ ) {
                var parameter = parameters[i];
                var parameterType = parameter.ParameterType;
                var dict = default(Dictionary<string, object>);
                if (_listType.IsAssignableFrom(parameterType)) {
                    dict = new Dictionary<string, object>() { {_dotNetTypeStr, _listStr} };
                    dict[_listStr] = GenerateTypeDictionary(parameterType.GetGenericArguments()[0]);
                } else if (_dictType.IsAssignableFrom(parameterType)) {
                    var arguments = parameterType.GetGenericArguments();
                    dict = new Dictionary<string, object>() { { _dotNetTypeStr, _dictStr } };
                    dict[_dictKey] = GenerateTypeDictionary(arguments[0]);
                    dict[_dictValue] = GenerateTypeDictionary(arguments[1]);
                } else dict = GenerateTypeDictionary(parameterType);
                parameterInfos.Add(dict);
            }
            return parameterInfos;
        }

        
        private Dictionary<string, object> GenerateTypeDictionary(Type type) {
            if (!type.CanInitType()) return new Dictionary<string, object>() { { _dotNetTypeStr, type.FullName } };
            var dict = new Dictionary<string, object>();
            var props = type.GetPropertiesEx();
            var isTypeDict = _dictType.IsAssignableFrom(type);
            var typeNamespace = _typeNamespaces.ContainsKey(type.Name) ? _typeNamespaces[type.Name] : _globalNamespace;
            if (isTypeDict) {
                var arguments = type.GetGenericArguments();
                var valueType = arguments[1];
                if (valueType.CanInitType())
                    dict["valueType"] = GenerateTypeDictionary(valueType);
                dict[_typeIdentifier] = "dict";
            }
            else {
                dict[_typeNameIdentifier] = type.Name;
                if (!String.IsNullOrWhiteSpace(typeNamespace)) dict[_typeNamespaceIdentifier] = typeNamespace;
                foreach (var prop in props) {
                    if (!prop.CanRead || !prop.CanWrite) continue;
                    var propType = prop.PropertyType;
                    var propName = prop.Name;
                    var defaultPropValue = ReflectionHelper.New(propType);
                    var value = default(object);
                    if (_listType.IsAssignableFrom(propType)) {
                        var genericTypes = propType.GetGenericArguments();
                        if (genericTypes != null && genericTypes.Length > 0) {
                            var list = ReflectionHelper.New(propType) as IList;
                            list.Add(ReflectionHelper.New(genericTypes[0]));
                            value = list;
                        }
                    }
                    else if (propType.CanInitType())
                        value = GenerateTypeDictionary(propType);
                    else value = defaultPropValue;
                    dict[propName] = value;
                }
            }
            return dict;
        }

        [DataMember]
        public string OperationName { get; set; }

        [DataMember]
        public string ServiceType { get; set; }

        [DataMember]
        public List<string> ParameterInfos { get; set; }
    }
}
